On-chain bitcoin payments#180
Merged
Merged
Conversation
Support on-chain bitcoin payments (issue #109) using the payments-rs OnChainProvider backed by the same LND wallet as the Lightning node. - lnvps_db: PaymentMethod::OnChain, ProviderConfig::OnChain(LndConfig), list_subscription_payments_by_method, unique index on subscription_payment.external_id (txid de-dupe) - Settings::get_onchain() mirrors get_node(); provider is required and injected into SubscriptionHandler like the Lightning node - Payment creation derives a fresh receive address (external_data); external_id stays empty until the deposit txid is known - OnChainPaymentHandler watcher: correlates deposits by address, de-dupes by txid (at-least-once stream -> exactly-once), pro-rates partial/late/over-payments and inserts new pro-rated renewal payments for deposits to already-settled addresses - API: onchain payment method, ApiPaymentData::OnChain { address }, admin payment method config support - MockDb::update_subscription_payment now mirrors all MySQL columns
The quote only fixes the price in the subscription's currency, never the BTC rate. When a deposit is discovered, time credited is pro-rated by the value received at the current rate: time_value * received_msat * rate_now / (expected_msat * rate_quoted) For BTC-denominated subscriptions this reduces to the plain msat ratio. The current rate is recorded on the settled payment, so address-reuse renewals also price against the latest settle. PricingEngine::get_ticker is now public for the watcher.
…utpoint
Per review:
- Discard the quote when a deposit is discovered (Detected, 0-conf) and
re-generate time/tax/fee/rate from the received amount through
PricingEngine::get_cost_by_amount - the same path as LNURL top-ups.
Confirmed then just settles, so time-to-confirm never matters.
Subscriptions without a VM (no amount->cost API) fall back to scaling
the original quote by value at the current rate.
- Key deposits by the standard outpoint {txid}:{vout} instead of txid
alone: one tx can pay several watched addresses. Requires payments-rs
0.5.0 which exposes vout on ChainPaymentUpdate.
- PricingEngine::get_ticker made public for the fallback path.
Deposits to addresses of deleted VMs cannot be credited automatically. Database-wise they are ignored - nothing is settled or recorded; a SendAdminNotification job is queued (VM id, user, outpoint, amount, receive address) and the sender is expected to contact support so it is resolved out of band. An in-memory seen-set stops stream replays from re-notifying within one process. Also fixes MockDb::get_vm_by_subscription to match the MySQL impl, which does not filter deleted VMs.
4 tasks
- New OnChainProviderConfig (url/cert/macaroon + address_type, account, min_confirmations with serde defaults) replaces the reused LndConfig in ProviderConfig::OnChain; serde tag fixed to 'onchain' to match provider_type() (rename_all would have produced 'on_chain') - PaymentMethodFactory: create_onchain_provider + get_onchain_provider_for_company, mapping OnChainAddressType to LndAddressType and passing account/min_confirmations through - Startup data migration seeds an OnChain config from the YAML LND section so it is manageable via the admin API - Admin API: SanitizedOnChainConfig exposes the new fields Note: runtime still builds the provider from YAML settings (like the Lightning node); completing the factory->runtime integration is #182. Cleanup: collapse nested ifs (let-chains), document that the pro_rate helpers only serve the no-VM fallback pending #181.
Lifecycle test now exercises the full on-chain flow with real nodes: - 14d happy path: renew?method=onchain derives a real address from LND, lnd-payer sends coins, a block is mined and the API's chain watcher settles the payment; expiry advances and the settled payment records the real txid:vout outpoint - 14e partial payment: half the quoted sats settles with exactly the received msats recorded and ~half an interval credited - 14f address reuse: a further deposit to the settled address auto-creates a new paid renewal payment - Falls back to admin-complete when the docker stack is unavailable Fixes a latent bug: the compose-file path in the docker helpers was relative but cargo runs tests with the crate dir as CWD, so pay_invoice had been silently falling back to admin-complete and lightning e2e never actually paid through LND. Both lightning and on-chain now settle via the real payer node (path resolved from CARGO_MANIFEST_DIR).
Drop the finished onchain-payments work file and all other work files marked complete (basic-firewall, dns-db-refactor, e2e-integration-tests, fw-inkernel-src-rate, referral-program-apis, vat-snapshot-and-vm-payment-retirement); in-progress files remain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #109
Adds on-chain bitcoin payments using the payments-rs
OnChainProvider(v0.4.1 from crates.io), backed by the same LND wallet as the Lightning node.Changes
DB (
lnvps_db)PaymentMethod::OnChainandProviderConfig::OnChain(LndConfig)(reuses the existing LND config)subscription_payment.external_id— the txid is stored here and uniqueness backs the stream de-duplicationlist_subscription_payments_by_method()for address correlation (external_datais encrypted, so matching happens in memory)Payment creation
Settings::get_onchain()mirrorsget_node(); the provider is required and injected intoSubscriptionHandlerexactly like the Lightning nodeexternal_data,external_idstays empty until the deposit is seenChain watcher (
payments/onchain.rs)amount/tax/time_valuescaled by received/expected)API
onchainpayment method selectable wherever methods are chosen; paymentdatareturns{ "onchain": { "address": "bc1…" } }onchainprovider type (sanitized like LND)Fixes
MockDb::update_subscription_paymentnow mirrors all columns the MySQL impl writes (previously droppedexternal_id, amounts, etc.)Testing